package com.yunkeji.api.pay.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.XmlUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;

import com.alibaba.fastjson.JSON;
import com.alipay.api.AlipayApiException;
import com.alipay.api.domain.AlipayTradeAppPayModel;
import com.alipay.api.request.AlipayTradeAppPayRequest;
import com.alipay.api.response.AlipayTradeAppPayResponse;
import com.baomidou.lock.annotation.Lock4j;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ijpay.alipay.AliPayApiConfigKit;
import com.ijpay.core.enums.SignType;
import com.ijpay.core.enums.TradeType;
import com.ijpay.core.kit.IpKit;
import com.ijpay.core.kit.WxPayKit;
import com.ijpay.wxpay.WxPayApi;
import com.ijpay.wxpay.model.UnifiedOrderModel;
import com.yunkeji.api.common.service.SysParamService;
import com.yunkeji.api.dataUpload.service.SysUploadService;
import com.yunkeji.api.invited.service.InvitedService;
import com.yunkeji.api.pay.bo.*;
import com.yunkeji.api.pay.service.PayService;
import com.yunkeji.api.pay.vo.PrePayVo;
import com.yunkeji.api.pay.vo.RechargeList;
import com.yunkeji.api.pay.vo.RechargePlanVo;
import com.yunkeji.api.pay.vo.VipPlanVo;
import com.yunkeji.api.test.TestController;
import com.yunkeji.api.user.mapper.ApiAppUserMapper;
import com.yunkeji.common.constant.CacheConstants;
import com.yunkeji.common.constant.SysConfigConsts;
import com.yunkeji.common.core.domain.dto.PayLogDTO;
import com.yunkeji.common.core.domain.event.PayLogEvent;
import com.yunkeji.common.core.domain.event.PushRollingEvent;
import com.yunkeji.common.core.page.TableDataInfo;
import com.yunkeji.common.enums.app.*;
import com.yunkeji.common.exception.AppServiceException;
import com.yunkeji.common.exception.ServiceException;
import com.yunkeji.common.helper.LoginHelper;
import com.yunkeji.common.msg.PushMessage;
import com.yunkeji.common.utils.*;
import com.yunkeji.common.utils.piggy.PiggyUtils;
import com.yunkeji.config.properties.PayProperties;
import com.yunkeji.dao.domain.*;
import com.yunkeji.dao.mapper.*;

import com.yunkeji.dao.vo.PayLogVo;
import com.yunkeji.dao.vo.PayWithdrawVo;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;

import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import javax.servlet.http.HttpServletRequest;


/**
 * @author 老张
 */
@Service
@RequiredArgsConstructor(onConstructor_ = @Autowired)
@Slf4j
public class PayServiceImpl implements PayService {
    private final PayRechargePlanMapper payRechargePlanMapper;
    private final PayDetailMapper payDetailMapper;
    private final PayProperties payProperties;
    private final PayLogMapper payLogMapper;
    private final ApiAppUserMapper apiAppUserMapper;
    private final AppUserExtMapper appUserExtMapper;
    private final AppUserWalletMapper appUserWalletMapper;
    private final PayWithDrawSignMapper payWithDrawSignMapper;
    private final PayWithDrawMapper payWithDrawMapper;
    private final PayVipPlanMapper payVipPlanMapper;
    @Autowired SysParamService sysParamService;
    private final PayWithdrawPlanMapper payWithdrawPlanMapper;
    private final SysUploadService uploadService;
    private final InvitedService invitedService;

    /**
     * 发起提现
     *
     * @author 老张
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void withdraw(WithDrawBo bo) {
        Long sendUserId = LoginHelper.getUserId();
        AppUser appUser = apiAppUserMapper.selectById(sendUserId);

        AppUserVip appUserVip = AppUserVip.builder().build().selectById(appUser.getId());

        if (!appUser.getCardAuth().equals(CheckStatus.审核通过.getCode())) {
            throw AppServiceException.paramException("未实名认证无法进行提现");
        }
        // Monsters(sudo rm -rf) 提现必须真人和实名都认证了才可以提现，
        if (!appUser.getVideoAuth().equals(CheckStatus.审核通过.getCode())) {
            throw AppServiceException.paramException("未真人认证无法进行提现");
        }
        // 是否电签
        PayWithDrawSign payWithDrawSign = payWithDrawSignMapper.selectById(LoginHelper.getUserId());
        if (payWithDrawSign == null
                || !payWithDrawSign.getStatus().equals(CheckStatus.审核通过.getCode())) {
            throw AppServiceException.needSign("需要电签才可发起提现");
        } else {
            boolean needToSign =
                    PiggyUtils.needToSign(
                            payWithDrawSign.getIdCardNo(), payWithDrawSign.getUserName());
            if (needToSign) {
                throw AppServiceException.needSign("电签信息已经过期,请前往电签");
            }
            if (!bo.getAccountName().equals(payWithDrawSign.getUserName())) {
                throw AppServiceException.paramException("收款人姓名与电签姓名不符,请重新填写,并确保收款人和收款账户开户人一致.");
            }
        }

        DateTime startWeek = DateUtil.beginOfWeek(new Date());
        DateTime endWeek = DateUtil.endOfWeek(new Date());

        PayWithDraw payWithDraw =
                payWithDrawMapper.selectOne(
                        Wrappers.lambdaQuery(PayWithDraw.class)
                                .eq(PayWithDraw::getCheckStatus, CheckStatus.审核通过.getCode())
                                .lt(PayWithDraw::getCreateTime, endWeek)
                                .gt(PayWithDraw::getCreateTime, startWeek)
                                .eq(PayWithDraw::getUserId, sendUserId),
                        false);

        if (payWithDraw != null
                && appUserVip.getExclusiveVipFlag().equals(EnableEnum.NO.getCode())) {
            // 非专属会员且本周已经提现一次
            throw AppServiceException.paramException("非专属会员,一周只能提现一次");
        }

        PayWithdrawPlan payWithdrawPlan = payWithdrawPlanMapper.selectById(bo.getPlanId());

        //    String byKey = sysParamService.selectConfigByKey(SysConfigConsts.提现比例);
        //   BigDecimal freeRate = new BigDecimal(byKey);

        AppUserWallet appUserWallet = appUserWalletMapper.selectById(sendUserId);
        // 提现比例=人民币 / 钻石 ( 人民币1元 / 砖石10个 = 0.1 )
        // 提现钻石金额=提现金额 / 提现比例
        //        BigDecimal withDrawDiamondBalance =
        //                BigDecimal.valueOf(bo.getNum()).divide(freeRate, 2, RoundingMode.UP);
        if (NumberUtil.isGreater(payWithdrawPlan.getDiamond(), appUserWallet.getDiamondBalance())) {
            throw AppServiceException.diamondNotEnoughException("余额不足");
        }
        // BigDecimal rate = new
        // BigDecimal(sysParamService.selectConfigByKey(SysConfigConsts.提现手续费));
        //  BigDecimal withDrawNum = BigDecimal.valueOf(bo.getNum());
        // 预计到账金额
        //        BigDecimal expectedNum =
        //                withDrawNum
        //                        .multiply(BigDecimal.valueOf(100).subtract(rate))
        //                        .divide(BigDecimal.valueOf(100), 2, RoundingMode.UP);

        PayWithDraw withDraw =
                PayWithDraw.builder()
                        .platformId(appUser.getPlatformId())
                        .phone(appUser.getPhone())
                        .expectedNum(payWithdrawPlan.getCnyPrice())
                        .rate(BigDecimal.ZERO)
                        .cnyPrice(payWithdrawPlan.getCnyPrice())
                        .diamondNum(payWithdrawPlan.getDiamond())
                        .payType(bo.getType())
                        .accountName(bo.getAccountName())
                        .branch(Integer.valueOf(ServletUtils.getRequest().getHeader("branch")))
                        .withdrawAccount(bo.getWithdrawAccount())
                        .userId(sendUserId)
                        .build();
        payWithDrawMapper.insert(withDraw);
        // 扣减钻石
        Integer integer =
                apiAppUserMapper.logSubDiamondBalance(
                        PayLogDTO.builder()
                                .traceId(withDraw.getId())
                                .uid(sendUserId)
                                .changeNum(payWithdrawPlan.getDiamond())
                                .serviceTag("用户提现-提现时间：" + withDraw.getCreateTime())
                                .logType(PayLogTypeEnums.消费_提现钻石)
                                .build());
        if (integer != 1) {
            log.error("余额不足");
            throw AppServiceException.diamondNotEnoughException("余额不足,请充值后再试");
        }
    }

    /**
     * 提现列表
     *
     * @author 老张
     * @param bo
     * @return
     */
    @Override
    public TableDataInfo<PayWithdrawVo> withdrawList(WithDrawListBo bo) {
        if(!StringUtils.isEmpty(bo.getQueryDate())){
            SimpleDateFormat format_2 = new SimpleDateFormat("yyyyMMdd");
            try {
                Date date = format_2.parse(bo.getQueryDate());
                bo.setStartDate(date);
                bo.setEndDate(DateUtils.addDays(date, 1));
            } catch (ParseException e) {
                e.printStackTrace();
            }
        }
        Page<PayWithdrawVo> payWithDrawPage =
                payWithDrawMapper.selectVoPage(
                        bo.build(),
                        Wrappers.lambdaQuery(PayWithDraw.class)
                                .gt(PayWithDraw::getCreateTime, bo.getStartDate())
                                .lt(PayWithDraw::getCreateTime, bo.getEndDate())
                                .eq(PayWithDraw::getUserId, LoginHelper.getUserId()));
        List<PayWithdrawVo> list = getPayWithdrawVos(payWithDrawPage);
        payWithDrawPage.setRecords(list);
        return TableDataInfo.build(payWithDrawPage);
    }

    @NotNull
    private static List<PayWithdrawVo> getPayWithdrawVos(Page<PayWithdrawVo> payWithDrawPage) {
        List<PayWithdrawVo> list = payWithDrawPage.getRecords();
        list.forEach(item -> {
            int status = item.getCheckStatus();
            switch (status){
                case 1:
                    item.setCheckStatus(0);
                    item.setStatus(0);
                    break;
                case 3:
                    item.setCheckStatus(1);
                    item.setStatus(1);
                    break;
                default:
                    item.setStatus(2);
                    break;
            }
        });
        return list;
    }

    /**
     * VIP计划
     *
     * @author 老张
     * @return
     */
    @Override
    public List<VipPlanVo> vipPlan() {
        List<PayVipPlan> payVipPlans = payVipPlanMapper.selectList();
        return BeanUtil.copyToList(payVipPlans, VipPlanVo.class);
    }

    /**
     * 成为会员
     *
     * @author 老张
     * @param bo
     * @return
     */
    @Override
    public PrePayVo becomeMember(PayBo bo) {
        PayVipPlan payVipPlan = payVipPlanMapper.selectById(bo.getPlanId());
        if (payVipPlan == null) {
            throw AppServiceException.paramException("计划无效");
        }
        PayTypeEnum byCode = PayTypeEnum.getByCode(bo.getPayType());
        PrePayVo vo = PrePayVo.builder().build();
        OrderResult result;
        switch (byCode) {
            case 微信支付:
                result =
                        wxPay(
                                "购买会员:" + payVipPlan.getMonthNum() + "个月",
                                "购买会员",
                                payVipPlan.getCnyPrice());
                break;
            case 支付宝支付:
                result =
                        aliPay(
                                "购买会员:" + payVipPlan.getMonthNum() + "个月",
                                "购买会员",
                                payVipPlan.getCnyPrice());
                break;
            default:
                throw AppServiceException.paramException("会员类型无效");
        }
        vo.setOrderInfo(result.getOrderInfo());

        PayDetail build =
                PayDetail.builder()
                        .platformId(LoginHelper.getPlatformId())
                        .planSnap(JSONUtil.toJsonStr(payVipPlan))
                        .userId(LoginHelper.getUserId())
                        .cnyPrice(payVipPlan.getCnyPrice())
                        .orderId(result.getOrderNum())
                        .branch(getBranch().getCode())
                        .status(PayStatusEnum.未支付.getCode())
                        .payType(bo.getPayType())
                        /** 充值类型1=金币2=会员 */
                        .rechargeType(2)
                        .month(payVipPlan.getMonthNum())
                        .build();
        payDetailMapper.insert(build);
        TestController.orderInfo = vo.getOrderInfo();
        return vo;
    }

    /**
     * @author 老张
     */
    @Override
    public TableDataInfo<RechargeList> rechargeList(RechargeBo bo) {
        Page<PayLog> payLogPage =
                payLogMapper.selectPage(
                        bo.build(),
                        Wrappers.lambdaQuery(PayLog.class)
                                .eq(PayLog::getLogType, PayLogTypeEnums.收益_充值金币.getCode())
                                .eq(PayLog::getLogDate, bo.getQueryDate())
                                .eq(PayLog::getUserId, LoginHelper.getUserId())
                                .orderByDesc(PayLog::getCreateTime));
        Page<RechargeList> page = BeanUtil.toBean(payLogPage, Page.class);
        page.setRecords(BeanUtil.copyToList(payLogPage.getRecords(), RechargeList.class));
        return TableDataInfo.build(page);
    }

    /**
     * @author 老张
     */
    @Override
    public TableDataInfo<PayLog> earnDetail(RechargeBo bo) {
        Page<PayLog> payLogPage =
                payLogMapper.selectPage(
                        bo.build(),
                        Wrappers.lambdaQuery(PayLog.class)
                                .ge(PayLog::getLogType, PayLogTypeEnums.收益_接受视频.getCode())
                                .eq(PayLog::getLogDate, bo.getQueryDate())
                                .eq(PayLog::getUserId, LoginHelper.getUserId())
                                .eq(PayLog::getStatus, 1)
                                //不显示预付费返还的记录，因为在支出里面已经进行了对应的合并扣除
                                .ne(PayLog::getLogType, PayLogTypeEnums.收益_通话预付费返还.getCode())
                                .orderByDesc(PayLog::getCreateTime));
        return TableDataInfo.build(payLogPage);
    }

    /**
     * @author 老张
     */
    @Override
    public TableDataInfo<PayLogVo> payDetail(RechargeBo bo) {
//        Page<PayLog> payLogPage =
//                payLogMapper.selectPage(
//                        bo.build(),
//                        Wrappers.lambdaQuery(PayLog.class)
//                                .lt(PayLog::getLogType, PayLogTypeEnums.收益_接受视频.getCode())
//                                .eq(PayLog::getLogDate, bo.getQueryDate())
//                                .eq(PayLog::getUserId, LoginHelper.getUserId())
//                                .orderByDesc(PayLog::getCreateTime));
        //合并了单次通话中多次的扣费
        return TableDataInfo.build(payLogMapper.selectPayLogByUserId(LoginHelper.getUserId(), bo.getQueryDate(), bo.build()));
    }

    /**
     * 小猪前去电签
     *
     * @author 老张
     */
    @Override
    public String piggyToSign(PiggyToSign piggyToSign) {
        PayWithDrawSign payWithDrawSign = payWithDrawSignMapper.selectById(LoginHelper.getUserId());
        if (payWithDrawSign == null) {
            payWithDrawSign = BeanUtil.toBean(piggyToSign, PayWithDrawSign.class);
            payWithDrawSign.setId(LoginHelper.getUserId());

            String url = piggyTosign(piggyToSign);
            if (StrUtil.isNotEmpty(url)) {
                payWithDrawSignMapper.insert(payWithDrawSign);
            }
            return url;
        } else {
            if (payWithDrawSign.getStatus().equals(CheckStatus.审核通过.getCode())) {
                throw AppServiceException.paramException("您已经通过电签,无需再次电签");
            }
            // 再次发起电签
            payWithDrawSign = BeanUtil.toBean(piggyToSign, PayWithDrawSign.class);
            payWithDrawSign.setId(LoginHelper.getUserId());
            String url = piggyTosign(piggyToSign);
            if (StrUtil.isNotEmpty(url)) {
                payWithDrawSignMapper.updateById(payWithDrawSign);
            }
            return url;
        }
    }

    @Override
    public void piggySignCallBack(JSONObject notify) {
        Long aLong = notify.getJSONObject("data").getJSONObject("customParams").getLong("uid");
        PayWithDrawSign payWithDrawSign = payWithDrawSignMapper.selectById(aLong);
        if (payWithDrawSign != null
                && CheckStatus.待审核.getCode().equals(payWithDrawSign.getStatus())) {
            Integer code = notify.getInt("code");
            if (0 == code) {
                payWithDrawSign.setStatus(CheckStatus.审核通过.getCode());
                payWithDrawSign.setSignInfo(notify.toString());
                payWithDrawSignMapper.updateById(payWithDrawSign);
            }
        }
    }

    /**
     * 小猪放款回调 https://openapi.xzsz.ltd/singleWithdrawal/CALLBACK.html
     *
     * @author 老张
     */
    @Override
    @Lock4j(
            name = CacheConstants.LOCK_PIGGY_PAY,
            keys = {"#key"})
    public boolean piggyPayCallBack(String notify) {
        JSONObject entries = PiggyUtils.analysisAesDataDev(notify);
        log.info("小猪放款回调解密后:{}", entries.toStringPretty());
        PayWithDraw outerTradeNo =
                payWithDrawMapper.selectOne(
                        Wrappers.lambdaQuery(PayWithDraw.class)
                                .eq(PayWithDraw::getOuterTradeNo, entries.getStr("outerTradeNo")));
        if (outerTradeNo != null) {
            //    通知类型 （ submitResult ：上报结果通知， tradeResult ：交易结果通知）
            if ("submitResult".equals(entries.getStr("notifyType"))) {
                if ("await".equals(entries.getStr("tradeStatus"))) {
                    SettleTypeEnums settleType  = entries.getStr("settleType").equals("bankcard") ? SettleTypeEnums.银行卡 : SettleTypeEnums.支付宝;
                    PiggyUtils.payConfirm(settleType, entries.getStr("outerTradeNo"));

                    // 为了避免重复支付始终返回成功
                    return true;
                }
            }

            if ("success".equals(entries.getStr("tradeStatus"))) {
                PayWithDraw build =
                        PayWithDraw.builder()
                                .payResult(entries.toString())
                                .payStatus(WithdrawPayStatus.已经到账.getCode())
                                .id(outerTradeNo.getId())
                                .build();
                Long userId = outerTradeNo.getUserId();
                AppUserWallet appUserWallet = appUserWalletMapper.selectById(userId);

                AppUserWallet appUserWalletNew = new AppUserWallet();
                appUserWalletNew.setWithdrawNum(appUserWallet.getWithdrawNum() + 1);
                appUserWalletNew.setId(userId);
                appUserWalletMapper.updateById(appUserWalletNew);

                payWithDrawMapper.updateById(build);
            } else {
                failHandler(entries, outerTradeNo);
            }

            return true;
        }

        return false;
    }

    private void failHandler(JSONObject entries, PayWithDraw outerTradeNo){
        // 第一次如果失败不会通知第二次,第一次成功就不管了等待第二次
        if ("failure".equals(entries.getStr("tradeStatus"))) {
            if (outerTradeNo.getRefundFlag().equals(EnableEnum.NO.getCode())) {
                PayWithDraw build =
                    PayWithDraw.builder()
                        .refundFlag(EnableEnum.YES.getCode())
                        .payResult(entries.toString())
                        .payStatus(WithdrawPayStatus.到账异常.getCode())
                        .id(outerTradeNo.getId())
                        .build();
                payWithDrawMapper.updateById(build);
                // 加钱
                apiAppUserMapper.logAddDiamondBalance(
                    PayLogDTO.builder()
                        .traceId(outerTradeNo.getId())
                        .uid(outerTradeNo.getUserId())
                        .changeNum(outerTradeNo.getDiamondNum())
                        .serviceTag("提现失败退款-提现时间:" + outerTradeNo.getCreateTime())
                        .logType(PayLogTypeEnums.收益_提现失败退款)
                        .build());
            } else {
                log.error("已经退款：{}", outerTradeNo.getOuterTradeNo());
            }
        }
    }

    @Override
    public List<PayWithdrawPlan> withdrawPlan() {
        return PayWithdrawPlan.builder()
                .build()
                .selectList(
                        Wrappers.lambdaQuery(PayWithdrawPlan.class)
                                .orderByAsc(PayWithdrawPlan::getCnyPrice));
    }

    /**
     * 小猪去电签
     *
     * @author 老张
     */
    private String piggyTosign(PiggyToSign piggyToSign) {
        //        boolean needToSign =
        //                PiggyUtils.needToSign(piggyToSign.getIdCardNo(),
        // piggyToSign.getUserName());
        //        if (needToSign) {
        //            return PiggyUtils.getSignUrl(
        //                    piggyToSign.getBankAccount(),
        //                    piggyToSign.getIdCardNo(),
        //                    piggyToSign.getMobile(),
        //                    piggyToSign.getUserName());
        //        } else {
        //            throw AppServiceException.paramException("暂无可电签信息");
        //        }
        // 有一个问题,我们之前有个app的用户已经在你们系统电签过了,然后在新的APP上已经电签过的,再来电签,你们那边会说"合同签署完毕",但是我们新APP,还没有这个人的具体的电签信息,你们只分主体,不分应用吗?
        // 我在调getSignUrl之前调了getSignStatus
        // 新APP的人在就=旧应用电签过了就没法调getSignUrl,可以直接调getSignUrl吗
        // 可以直接调的， 用户同样会获取到链接，   如果这个人已经签署过了，会直接跳转到签署成功跳转页面
        return PiggyUtils.getSignUrl(
                piggyToSign.getBankAccount(),
                piggyToSign.getIdCardNo(),
                piggyToSign.getMobile(),
                piggyToSign.getUserName());
    }

    /**
     * 获取branch
     *
     * @author 老张
     * @return
     */
    private AppBranchEnum getBranch() {
        ServletRequestAttributes servletRequestAttributes =
                (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = servletRequestAttributes.getRequest();
        String branch = request.getHeader("branch");
        return AppBranchEnum.getByCode(Integer.parseInt(branch));
    }

    private String getOaid() {
        ServletRequestAttributes servletRequestAttributes =
            (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = servletRequestAttributes.getRequest();
        return request.getHeader("oaid");
    }

    /**
     * 获取充值计划
     *
     * @author 老张
     * @return
     */
    @Override
    public List<RechargePlanVo> rechargePlan() {
        List<PayRechargePlan> payRechargePlans = payRechargePlanMapper.selectList();
        return BeanUtil.copyToList(payRechargePlans, RechargePlanVo.class);
    }

    /**
     * 充值金币
     *
     * @author 老张
     * @param bo
     */
    @Override
    @SneakyThrows
    public PrePayVo rechargeGoldPre(PayBo bo) {
        PayRechargePlan payRechargePlan = payRechargePlanMapper.selectById(bo.getPlanId());
        if (payRechargePlan == null) {
            throw AppServiceException.paramException("充值计划未找到");
        }

        PayTypeEnum byCode = PayTypeEnum.getByCode(bo.getPayType());
        PrePayVo vo = PrePayVo.builder().build();
        OrderResult result;
        switch (byCode) {
            case 微信支付:
                result =
                        wxPay(
                                "购买金币:" + payRechargePlan.getGold(),
                                "购买金币",
                                payRechargePlan.getCnyPrice());
                break;
            case 支付宝支付:
                result =
                        aliPay(
                                "购买金币:" + payRechargePlan.getGold(),
                                "购买金币",
                                payRechargePlan.getCnyPrice());
                break;
            default:
                throw AppServiceException.paramException("充值类型未找到");
        }
        vo.setOrderInfo(result.getOrderInfo());
        PayDetail build =
                PayDetail.builder()
                        .platformId(LoginHelper.getPlatformId())
                        .planSnap(JSONUtil.toJsonStr(payRechargePlan))
                        .userId(LoginHelper.getUserId())
                        .cnyPrice(payRechargePlan.getCnyPrice())
                        .orderId(result.getOrderNum())
                        .branch(getBranch().getCode())
                        .oaid(getOaid())
                        .status(PayStatusEnum.未支付.getCode())
                        .gold(payRechargePlan.getGold())
                        .payType(byCode.getCode())
                        .build();
        payDetailMapper.insert(build);
        TestController.orderInfo = vo.getOrderInfo();
        return vo;
    }

    /** 通过订单号进行查询 **/
    public PayDetail findByOutTradeNo(String outTradeNo){
        return payDetailMapper.selectOne(
                Wrappers.lambdaQuery(PayDetail.class)
                    .eq(PayDetail::getOrderId, outTradeNo));
    }

    /**
     * 充值成功-兼容微信充值,支付宝充值
     *
     * @author 老张
     * @param bo
     */
    @Override
    @Lock4j(
            name = CacheConstants.CALL_BACK_RECHARGE,
            keys = {"#bo.outTradeNo"})
    @SneakyThrows
    public void rechargeSuccess(RechargeSuccessBo bo) {
        PayDetail payDetail =
                payDetailMapper.selectOne(
                        Wrappers.lambdaQuery(PayDetail.class)
                                .eq(PayDetail::getOrderId, bo.getOutTradeNo()));
        if (payDetail == null) {
            throw AppServiceException.paramException("订单未找到");
        }
        if (payDetail.getStatus() != PayStatusEnum.未支付.getCode()) {
            throw AppServiceException.paramException("订单状态异常");
        }

        Integer rechargeType = payDetail.getRechargeType();
        /** 充值类型1=金币2=会员 */
        switch (rechargeType) {
            case 1:
                callBackRechargeGold(payDetail, bo);
                break;
            case 2:
                callBackRechargeVip(payDetail, bo);
                break;
            default:
                throw AppServiceException.paramException("充值回调异常");
        }
        if (!StringUtils.isEmpty(payDetail.getOaid())) {
            // 上报数据
            uploadService.upload(payDetail.getOaid(), DataUploadUtils.DataType.支付, payDetail.getBranch());
        }
    }

    /**
     * 充值vip回调
     *
     * @author 老张
     */
    private void callBackRechargeVip(PayDetail payDetail, RechargeSuccessBo bo) {
        AppUser appUser = apiAppUserMapper.selectById(payDetail.getUserId());
        AppUserExt appUserExt = appUserExtMapper.selectById(payDetail.getUserId());
        AppUserVip appUserVip = AppUserVip.builder().build().selectById(appUser.getId());

        Date beforeTime = null;
        Date afterTime = null;

        String planSnap = payDetail.getPlanSnap();
        PayVipPlan payVipPlan = JSONUtil.toBean(planSnap, PayVipPlan.class);

        Integer vipType = payVipPlan.getVipType();

        PayLogTypeEnums payLogTypeEnums = PayLogTypeEnums.getByCode(vipType);
        if (payLogTypeEnums.getCode().equals(PayLogTypeEnums.收益_充值专属VIP.getCode())) {
            Integer exclusiveVipFlag = appUserVip.getExclusiveVipFlag();
            beforeTime = appUserVip.getExclusiveVipExpireTime();
            if (EnableEnum.YES.getCode().equals(exclusiveVipFlag)) {
                // 之前是vip
                Date vipExpireTime = appUserVip.getExclusiveVipExpireTime();
                DateTime dateTime = DateUtil.offsetMonth(vipExpireTime, payVipPlan.getMonthNum());
                appUserVip.setExclusiveVipExpireTime(dateTime);
            } else {
                DateTime dateTime = DateUtil.offsetMonth(new Date(), payVipPlan.getMonthNum());
                appUserVip.setExclusiveVipExpireTime(dateTime);
            }
            appUserVip.setExclusiveVipFlag(EnableEnum.YES.getCode());
            afterTime = appUserVip.getExclusiveVipExpireTime();
        } else {
            Integer commonVipFlag = appUserVip.getCommonVipFlag();
            beforeTime = appUserVip.getCommonVipExpireTime();
            if (EnableEnum.YES.getCode().equals(commonVipFlag)) {
                // 之前是vip
                Date vipExpireTime = appUserVip.getCommonVipExpireTime();
                DateTime dateTime = DateUtil.offsetMonth(vipExpireTime, payVipPlan.getMonthNum());
                appUserVip.setCommonVipExpireTime(dateTime);
            } else {
                DateTime dateTime = DateUtil.offsetMonth(new Date(), payVipPlan.getMonthNum());
                appUserVip.setCommonVipExpireTime(dateTime);
            }
            appUserVip.setCommonVipFlag(EnableEnum.YES.getCode());
            afterTime = appUserVip.getCommonVipExpireTime();
        }
        appUserVip.updateById();
        // 插入支付日志
        SpringUtil.publishEvent(
                PayLogEvent.builder()
                        .logDate(DateUtil.format(new Date(), DatePattern.PURE_DATE_PATTERN))
                        .userId(payDetail.getUserId())
                        .logType(payLogTypeEnums.getCode())
                        .symbol("+")
                        .balanceType(BalanceTypeEnums.金币.getCode())
                        .remark(payLogTypeEnums.name())
                        .changeNum(BigDecimal.valueOf(payDetail.getMonth()))
                        .build());
        // 更新支付订单
        payDetail.setBeforeExpireTime(beforeTime);
        payDetail.setAfterExpireTime(afterTime);
        payDetail.setMonth(payDetail.getMonth());
        payDetail.setThirdId(bo.getTradeNo());
        payDetail.setStatus(PayStatusEnum.已支付.getCode());
        payDetailMapper.updateById(payDetail);

        PushMessage.sendSysNotify(appUser.getPlatformId(), "恭喜您，充值成功!您充值的会员，已到账，请前往个人中心查看!");
        // 跑马灯推送
        SpringUtil.publishEvent(
                PushRollingEvent.builder()
                        .type(PushRollingEvent.Type.会员.getCode())
                        .nickName(appUserExt.getNickName())
                        .headUrl(appUserExt.getHeadUrl())
                        .build());
    }

    /**
     * 充值金币回调
     *
     * @author 老张
     */
    private void callBackRechargeGold(PayDetail payDetail, RechargeSuccessBo bo) {
        AppUser appUser = apiAppUserMapper.selectById(payDetail.getUserId());
        AppUserExt appUserExt = appUserExtMapper.selectById(payDetail.getUserId());
        AppUserWallet appUserWalletOld = appUserWalletMapper.selectById(payDetail.getUserId());
        // 加钱
        apiAppUserMapper.logForAddGoldBalance(
                PayLogDTO.builder()
                        .traceId(payDetail.getId())
                        .uid(payDetail.getUserId())
                        .changeNum(payDetail.getGold())
                        .serviceTag("用户充值金币:" + payDetail.getOrderId())
                        .logType(PayLogTypeEnums.收益_充值金币)
                        .build());
        // 累计充值
        apiAppUserMapper.addGoldCount(payDetail.getUserId(), payDetail.getGold(), 1);
        AppUserWallet appUserWalletNew = appUserWalletMapper.selectById(payDetail.getUserId());

        // 更新支付订单
        payDetail.setBeforeGoldBalance(appUserWalletOld.getGoldBalance());
        payDetail.setAfterGoldBalance(appUserWalletNew.getGoldBalance());
        payDetail.setGold(payDetail.getGold());
        payDetail.setThirdId(bo.getTradeNo());
        payDetail.setStatus(PayStatusEnum.已支付.getCode());
        payDetailMapper.updateById(payDetail);

        PushMessage.sendSysNotify(
                appUser.getPlatformId(),
                "恭喜您，充值成功!您充值的" + payDetail.getGold() + "个金币，已到账，请前往我的钱包查看!");

        // 跑马灯推送
        SpringUtil.publishEvent(
                PushRollingEvent.builder()
                        .type(PushRollingEvent.Type.充值.getCode())
                        .nickName(appUserExt.getNickName())
                        .headUrl(appUserExt.getHeadUrl())
                        .content(JSONUtil.createObj().set("num", payDetail.getGold()))
                        .build());

        // 充值奖励
        if(!Objects.isNull(appUser.getPid()) && appUser.getPid() > 0){
            Long count = payDetailMapper.selectCount(
                Wrappers.lambdaQuery(PayDetail.class)
                    .eq(PayDetail::getUserId, payDetail.getUserId())
                    .eq(PayDetail::getStatus, PayStatusEnum.已支付.getCode()));
            //应用邀请奖励
            if(count <= 1){
                invitedService.inviteRewardFirst(payDetail.getId(), payDetail.getGold(), appUser.getPid());
            }
            invitedService.inviteRewardRecharge(payDetail.getId(), payDetail.getGold(), appUser.getPid());
        }
//        rechargeReward(payDetail);
    }

    /**
     * 充值返利 有舍有得-邀请奖励平台出
     *
     * @author 老张
     */
    private void rechargeReward(PayDetail payDetail) {
        Long benefitUid = payDetail.getUserId();
        AppUser appUser = apiAppUserMapper.selectById(benefitUid);
        Long pid = appUser.getPid();
        if (pid == null) {
            return;
        }
        BigDecimal withDrawRate =
                new BigDecimal(sysParamService.selectConfigByKey(SysConfigConsts.邀请充值奖励));
        // 收益比例
        BigDecimal rate = withDrawRate.divide(BigDecimal.valueOf(100), 2, RoundingMode.DOWN);

        BigDecimal earnCost = payDetail.getGold();
        // 收益乡下取整
        BigDecimal earn = earnCost.multiply(rate).setScale(0, RoundingMode.DOWN);

        if (NumberUtil.isGreaterOrEqual(earn, BigDecimal.ONE)) {
            // 邀请人加钱

            apiAppUserMapper.logAddDiamondBalance(
                    PayLogDTO.builder()
                            .traceId(payDetail.getId())
                            .uid(appUser.getPid())
                            .changeNum(earn)
                            .serviceTag("用户充值-邀请人收益-充值流水号:" + payDetail.getOrderId())
                            .logType(PayLogTypeEnums.收益_邀请充值收益)
                            .build());
        }
    }

    /**
     * @author 老张
     * @param body 对一笔交易的具体描述信息。如果是多种商品，请将商品描述字符串累加传给body。
     * @param subject 商品标题/交易标题/订单标题/订单关键字等。
     * @param total 订单总金额，单位为人民币（元），取值范围为 0.01~100000000.00，精确到小数点后两位。
     * @return
     */
    @SneakyThrows
    public OrderResult aliPay(String body, String subject, BigDecimal total) {
        AlipayTradeAppPayModel model = new AlipayTradeAppPayModel();
        model.setBody(body);
        model.setSubject(subject);
        // 商户订单号，由商家自定义，需保证商家系统中唯一。仅支持数字、字母、下划线。
        String orderNum = "Z" + DateUtil.current() + RandomUtil.randomNumbers(8);
        model.setOutTradeNo(orderNum);
        model.setTimeoutExpress("1m");
        model.setTotalAmount(total.toString());
        // 回调参数
        model.setPassbackParams("");
        // 产品代码
        model.setProductCode("QUICK_MSECURITY_PAY");
//        ServletRequestAttributes servletRequestAttributes =
//            (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
//        HttpServletRequest req = servletRequestAttributes.getRequest();
        PayProperties.Ali ali = payProperties.getAli();
        String appid;
//        if(req.getHeader("branch").startsWith("3")){
//            appid = ali.getAppidTwo();
//        } else {
            appid = ali.getAppid();
//        }
        try {
            AlipayTradeAppPayRequest request = new AlipayTradeAppPayRequest();
            request.setBizModel(model);
            request.setNotifyUrl(ali.getNotifyUrl());
            AlipayTradeAppPayResponse response = AliPayApiConfigKit.getApiConfig(appid).getAliPayClient().sdkExecute(request);
            String orderInfo = response.getBody();
            OrderResult build = new OrderResult().setOrderInfo(orderInfo).setOrderNum(orderNum);
            return build;
        } catch (AlipayApiException e) {
            log.error("支付宝支付异常:{},{}", e.getErrMsg(), e.getMessage());
            throw AppServiceException.paramException("支付异常");
        }
    }

    /**
     * @author 老张
     * @param body 对一笔交易的具体描述信息。如果是多种商品，请将商品描述字符串累加传给body。
     * @param subject 商品标题/交易标题/订单标题/订单关键字等。
     * @param total 订单总金额，单位为人民币（元），取值范围为 0.01~100000000.00，精确到小数点后两位。
     * @return
     */
    public OrderResult wxPay(String body, String subject, BigDecimal total) {
        throw AppServiceException.paramException("暂时不支持微信支付，请使用支付宝进行支付！联系客服代充可享受更多优惠。");
//        ServletRequestAttributes servletRequestAttributes =
//                (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
//        HttpServletRequest request = servletRequestAttributes.getRequest();
//        String ip = IpKit.getRealIp(request);
//        if (StrUtil.isBlank(ip)) {
//            ip = "127.0.0.1";
//        }
//        //WxPayApiConfig wxPayApiConfig = WxPayApiConfigKit.getWxPayApiConfig();
//        // 进行区别配置参数
//        PayProperties.Wx wx = payProperties.getWx();
//        String domain = wx.getNotifyUrl(), appid, mchId, partnerKey;
////        if(request.getHeader("branch").startsWith("3")){
////            appid = wx.getAppidTwo();
////            mchId = wx.getMchIdTwo();
////            partnerKey = wx.getApiKey();
////        } else {
//            appid = wx.getAppid();
//            mchId = wx.getMchId();
//            partnerKey = wx.getPartnerKey();
////        }
//        String orderNum = "W" + DateUtil.current() + RandomUtil.randomNumbers(8);
//        System.err.println(domain);
//        Map<String, String> params =
//                UnifiedOrderModel.builder()
//                        .appid(appid)
//                        .mch_id(mchId)
//                        .nonce_str(WxPayKit.generateStr())
//                        .body(body)
//                        .attach(subject)
//                        .out_trade_no(orderNum)
//                        // 单位分 且不带小数点
//                        .total_fee(total.multiply(BigDecimal.valueOf(100)).longValue() + "")
//                        .spbill_create_ip(ip)
//                        .notify_url(domain)
//                        .trade_type(TradeType.APP.getTradeType())
//                        .build()
//                        .createSign(partnerKey, SignType.HMACSHA256);
//
//        String xmlResult = WxPayApi.pushOrder(false, params);
//        log.info("微信支付预支付:{}", XmlUtil.xmlToMap(xmlResult));
//        Map<String, String> result = WxPayKit.xmlToMap(xmlResult);
//        String returnCode = result.get("return_code");
//        String returnMsg = result.get("return_msg");
//        if (!WxPayKit.codeIsOk(returnCode)) {
//            throw AppServiceException.paramException(returnMsg + ":微信支付调起失败:" + returnCode);
//        }
//        String resultCode = result.get("result_code");
//        if (!WxPayKit.codeIsOk(resultCode)) {
//            throw AppServiceException.paramException(returnMsg + ":微信支付调起失败:" + resultCode);
//        }
//        // 以下字段在 return_code 和 result_code 都为 SUCCESS 的时候有返回
//        String prepayId = result.get("prepay_id");
//
//        Map<String, String> packageParams =
//                WxPayKit.appPrepayIdCreateSign(
//                        appid,
//                        mchId,
//                        prepayId,
//                        partnerKey,
//                        SignType.HMACSHA256);
//
//        String jsonStr = JSON.toJSONString(packageParams);
//        log.info("微信 orderInfo：{}", jsonStr);
//        return new OrderResult().setOrderInfo(jsonStr).setOrderNum(orderNum);
    }

    /**
     * 充值回调
     *
     * @author 老张
     */
    public OrderResult rechargeCallback(PayTypeEnum payTypeEnum) {
        return new OrderResult();
    }

    /**
     * 订单结果
     *
     * @author 老张
     */
    @Data
    @Accessors(chain = true)
    public class OrderResult {
        private String orderNum;
        private String orderInfo;
    }
}
